/*
* Copyright 2016 requery.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.requery.sql;
import io.requery.EntityCache;
import io.requery.Transaction;
import io.requery.TransactionException;
import io.requery.TransactionIsolation;
import io.requery.TransactionListener;
import io.requery.meta.Type;
import io.requery.proxy.EntityProxy;
import io.requery.util.Objects;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.TransactionSynchronizationRegistry;
import javax.transaction.UserTransaction;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collection;
/**
* Transaction for use when running in a container or managed environment.
*
* @author Nikhil Purushe
*/
class ManagedTransaction implements EntityTransaction, ConnectionProvider, Synchronization {
private final ConnectionProvider connectionProvider;
private final TransactionListener transactionListener;
private final TransactionEntitiesSet entities;
private Connection connection;
private Connection uncloseableConnection;
private TransactionSynchronizationRegistry registry;
private UserTransaction userTransaction;
private boolean committed;
private boolean rolledBack;
private boolean initiatedTransaction;
private boolean completed;
ManagedTransaction(TransactionListener transactionListener,
ConnectionProvider connectionProvider,
EntityCache cache) {
this.transactionListener = Objects.requireNotNull(transactionListener);
this.connectionProvider = Objects.requireNotNull(connectionProvider);
this.entities = new TransactionEntitiesSet(cache);
}
private TransactionSynchronizationRegistry getSynchronizationRegistry() {
if (registry == null) {
try {
registry = InitialContext.doLookup("java:comp/TransactionSynchronizationRegistry");
} catch (NamingException e) {
throw new TransactionException(e);
}
}
return registry;
}
private UserTransaction getUserTransaction() {
if (userTransaction == null) {
try {
userTransaction = InitialContext.doLookup("java:comp/UserTransaction");
} catch (NamingException e) {
throw new TransactionException(e);
}
}
return userTransaction;
}
@Override
public Connection getConnection() {
return uncloseableConnection;
}
@Override
public Transaction begin() {
if (active()) {
throw new IllegalStateException("transaction already active");
}
transactionListener.beforeBegin(null);
int status = getSynchronizationRegistry().getTransactionStatus();
if (status == Status.STATUS_NO_TRANSACTION) {
try {
getUserTransaction().begin();
initiatedTransaction = true;
} catch (NotSupportedException | SystemException e) {
throw new TransactionException(e);
}
}
getSynchronizationRegistry().registerInterposedSynchronization(this);
try {
connection = connectionProvider.getConnection();
} catch (SQLException e) {
throw new TransactionException(e);
}
uncloseableConnection = new UncloseableConnection(connection);
committed = false;
rolledBack = false;
entities.clear();
transactionListener.afterBegin(null);
return this;
}
@Override
public Transaction begin(TransactionIsolation isolation) {
if (isolation != null) {
throw new TransactionException("isolation can't be specified in managed mode");
}
return begin();
}
@Override
public void close() {
if (connection != null) {
if (!committed && !rolledBack) {
rollback();
}
try {
connection.close();
} catch (SQLException ignored) {
} finally {
connection = null;
}
}
}
@Override
public void commit() {
if (initiatedTransaction) {
try {
transactionListener.beforeCommit(entities.types());
getUserTransaction().commit();
transactionListener.afterCommit(entities.types());
} catch (RollbackException | SystemException | HeuristicMixedException |
HeuristicRollbackException e) {
throw new TransactionException(e);
}
}
try {
entities.clear();
} finally {
close();
}
}
@Override
public void rollback() {
if (!rolledBack) {
try {
if (!completed) {
transactionListener.beforeRollback(entities.types());
if (initiatedTransaction) {
try {
getUserTransaction().rollback();
} catch (SystemException e) {
throw new TransactionException(e);
}
} else if (active()) {
getSynchronizationRegistry().setRollbackOnly();
}
transactionListener.afterRollback(entities.types());
}
} finally {
rolledBack = true;
entities.clearAndInvalidate();
}
}
}
@Override
public boolean active() {
TransactionSynchronizationRegistry registry = getSynchronizationRegistry();
return registry != null && registry.getTransactionStatus() == Status.STATUS_ACTIVE;
}
@Override
public void beforeCompletion() {
}
@Override
public void afterCompletion(int status) {
switch (status) {
case Status.STATUS_ROLLEDBACK:
case Status.STATUS_MARKED_ROLLBACK:
case Status.STATUS_ROLLING_BACK:
rollback();
close();
break;
}
completed = true;
}
@Override
public void addToTransaction(EntityProxy<?> proxy) {
entities.add(proxy);
}
@Override
public void addToTransaction(Collection<Type<?>> types) {
entities.types().addAll(types);
}
}